/**
 * Definition of the Extractor class
 */
package org.cholm.drtest;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.Map;

// import org.apache.commons.logging.Log;
// import org.apache.commons.logging.LogFactory;
import org.apache.http.client.utils.URIUtils;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONObject;

public class Extractor {
	/** Base URL of API */
	public static final String API_URL = "http://dr.dk/nu/api/";
	/** Base URL of Mobile API */
	public static final String API_MOBIL_URL = "http://dr.dk/nu-mobil/api/";
	/** Base URL of Meta data */
	public static final String META_URL = "http://www.dr.dk/mu/programcard/";
	/** Width of images */
	public static final int IMG_WIDTH = 120;
	/** Height of images */
	public static final int IMG_HEIGHT = 90;
	/**
	 * Run this
	 *
	 * @return true on success
	 */
	public boolean run()
	{
		System.out.println("=== Getting genres");
		getGenres(true);

		System.out.println("=== Getting channels");
		getChannels();

		System.out.println("=== Getting letters");
		getLetters();
		// print();
		menu();
		return true;
	}
	/**
	 * Method to fill series from a query into a list
	 *
	 * @param query Query
	 * @param list  List to fill
	 *
	 * @return true on success
	 */
	protected boolean fillList(String query,
							   List<Serie> list,
						       boolean alsoVideos,
							   boolean alsoLinks)
	{
		// Do the query
		JSONObject object = null;
		if ((object = getJSONObject(query)) == null) return false;

		// Get the programs
		JSONArray l = object.optJSONArray("ProgramSeries");
		if (l == null) {
			System.err.println("Didn't get the list of series");
			return false;
		}

		// Loop over programs
		for (int i = 0; i < l.length(); i++) {
			// Get the object
			JSONObject o = l.optJSONObject(i);
			if (o == null) {
				System.err.println("Didn't get serie at " + i);
				continue;
			}

			// Find the slug
			String slug = o.optString("ProgramSeriesSlug");
			if (slug == null || slug.isEmpty()) {
				System.err.println("Didn't get slug of serie: " + o.toString());
				continue;
			}

			// See if we have the series in the cache already
			Serie s = series.get(slug);
			if (!(s == null)) {
				if (list != null) list.add(s);
				continue;
			}

			// Create new series object
			s = new Serie(o);
			if (!s.isValid()) {
				System.err.println("Serie " + slug + " invalid: " + o.toString());
				continue;
			}
			// If we asked to fill video information, do it here
			if (alsoVideos) fillPrograms(s, alsoLinks);

			// Add it to our list and cache
			if (list != null) list.add(s);
			series.put(slug, s);

			// Register with genre
			genres.put(s.getGenre(), s);

			// Register with letter
			letters.put(s.getLetter(), s);

			// Register with channel
			channels.put(s.getChannel(), s);
		}

		return true;
	}
	/**
	 * Fill in video information
	 *
	 * @param s          Serie
	 * @param alsoLinks  If true, also find links
	 *
	 * @return true on success
	 */
	protected boolean fillPrograms(Serie s, boolean alsoLinks) {
		if (s.getVideos().size() > 0) return true;

		JSONObject object = null;
		if ((object = getJSONObject(API_MOBIL_URL + "programserie?slug=" +
									s.getSlug())) == null) return false;

		String tit = object.optString("programSerieTitle");
		if (tit != null && !tit.isEmpty()) s.title = tit;

		JSONArray videos = object.optJSONArray("videos");
		for (int i = 0; i < videos.length(); i++) {
			JSONObject o = videos.optJSONObject(i);
			if (o == null) {
				System.err.println("No JSON object at " + i + ": " + videos.toString());
				continue;
			}
			Program v = new Program(o);
			if (alsoLinks) {
				if (!fillLinks(v)) {
					System.err.println("No links for " + v.getId());
					continue;
				}
			}
			if (!v.isValid(alsoLinks)) {
				System.err.println("Invalid video @ " + i + ": " + o.toString());
				continue;
			}
			s.videos.add(v);
		}
		return true;
	}
	/**
	 * Fill in link information
	 *
	 * @param s  Video to process
	 *
	 * @return true on success
	 */
	protected boolean fillLinks(Program s) {
		// If we already have links, do nothing
		if (s.hasLink()) return true;

		// Run the query
		JSONObject object = null;
		if ((object = getJSONObject(API_MOBIL_URL + "videos/" + s.getId()
										  + "?platform=android")) == null)
			return false;

		JSONObject streams = object.optJSONObject("streams");
		if (streams == null) {
			System.err.println("Didn't get list of streams");
			return false;
		}
		s.setLinks(streams);

		return s.hasLink();
	}
	/**
	 * Method to fill in the genres
	 *
	 * @param fill
	 */
	protected void getGenres(boolean fill)
	{
		// Get list of genres
		JSONArray array = null;
		if ((array = getJSONArray(API_MOBIL_URL+"genres")) == null) return;

		// Loop over genres
		for (int i = 0; i < array.length(); i++) {
			JSONObject obj = array.optJSONObject(i);
			if (obj == null) continue;
			String name = obj.optString("name");

			if (name == null || name.isEmpty()) continue;
			genres.put(name, null);

			if (fill) fillGenre(name);
		}
	}
	/**
	 * Method to populate the genre
	 *
	 * @param genre
	 */
	protected void fillGenre(String genre)
	{
		List<Serie> m = genres.get(genre);
		if (m == null) {
			System.out.println("Unknown genre: " + genre);
			return;
		}
		if (m.size() > 0) return;
		String query = queryUrl("",genre,"",false,true,50,0);
		fillList(query, null, false, false);
	}
	protected void getChannels() {
		String[] ch = {"dr1", "dr2", "dr-k", "dr3", "dr-ultra", "dr-ramasjang" };
		for (String c :ch) {
			if (channels.get(c) == null) channels.put(c, null);
			fillChannel(c);
		}
	}
	protected void fillChannel(String ch) {
		List<Serie> l = channels.get(ch);
		if (l == null) {
			System.err.println("Unknown channel " + ch);
			return;
		}
		final int max = 100;
		if (l.size() > max) return;
		String query = queryUrl("","",ch,false,true,max,l.size());
		fillList(query, null, false, false);
	}
	protected void getLetters() {
		String l = "abcdefghijklmnopqrstuvwxyzæøå";
		for (char c : l.toCharArray()) {
			String sc = Character.toString(c);
			if (letters.get(sc) == null) letters.put(sc, null);
			fillLetter(sc);
		}
	}
	protected void fillLetter(String sc) {
		// String sc = Character.toString(c);
		List<Serie> l = letters.get(sc);
		if (l == null) {
			System.err.println("Unknown Letter " + sc);
			return;
		}
		final int max = 30;
		if (l.size() > max) return;
		String query = queryUrl(sc,"","",false,true,30,l.size());
		fillList(query, null, false, false);
	}
	/**
	 * Method to populate newest
	 */
	protected void getNewest()
	{
		String query = API_MOBIL_URL + "newest";
		fillList(query, newest, true, true);
	}
	/**
	 * Print everything
	 */
	public void print()
	{
		Printer p = new Printer();
		p.begin("Alphabetically");
		p.print(letters, (Printer.SHOW_DETAILS|
						  Printer.SHOW_SERIES|
						  Printer.SHOW_VIDEOS|
						  Printer.SHOW_STREAMS|
						  Printer.SHOW_META));
		p.end("End of alphabetic list");

		p.begin("Genres");
		p.print(genres, (Printer.SHOW_DETAILS|
				         Printer.SHOW_SERIES));
		p.end("End of genre list");

		p.begin("Channels");
		p.print(channels, (Printer.SHOW_DETAILS|
						   Printer.SHOW_SERIES));
		p.end("End of channel list");
	}
	/**
	 * Utility to get a JSON object from a URL
	 *
	 * @param url Source URL
	 *
	 * @return Retrieved object or null
	 */
	protected JSONObject getJSONObject(String url)
	{
		RestClient.Result r   = rest.get(url);
		if (r.object == null) {
			System.err.println("Failed to get JSON object: " + url);
			return null;
		}
		return r.object;
	}
	/**
	 * Utility to get an array JSON objects from a URL
	 *
	 * @param url Source URL
	 *
	 * @return Retrieved object or null
	 */
	protected JSONArray getJSONArray(String url)
	{
		RestClient.Result r   = rest.get(url);
		if (r.array == null) {
			System.err.println("Failed to get JSON object: " + url);
			return null;
		}
		return r.array;
	}
	/**
	 * Get an option string
	 *
	 * @param key   Key
	 * @param value Value
	 *
	 * @return Query parameter
	 */
	protected String getQueryParam(String key, String value)
	{
		return ("&" + key + "=" + ((value==null) ? "" : value));
	}
	/**
	 * Form a query URL
	 *
	 * @param title     Title selection
	 * @param genre     Genre selection
	 * @param channel   Channel selection
	 * @param previews  Whether to include previews
	 * @param byDate    Whether to sort by date
	 * @param limit     Maximum number of results to retrieve
	 * @param offset    Off-set in result list
	 *
	 * @return The query URL
	 */
	@SuppressWarnings("deprecation")
	protected String queryUrl(String title,
							String genre,
						   String channel,
						   boolean previews,
						   boolean byDate,
						   int     limit,
						   int     offset)
	{
		// date=true                            -> OK
		// date=true  + channel                 -> OK
		// date=true  + genre                   -> OK
		// date=true  + title                   -> OK (title ignored)
		// date=true  + channel + title         -> OK
		// date=true  + channel + genre         -> OK
		// date=true  + genre + title           -> OK (title ignored)
		// date=true  + channel + genre + title -> OK (channel+title ignored)
		// date=false                           -> empty
		// date=false + channel                 -> empty
		// date=false + genre                   -> empty
		// date=false + title                   -> OK
		// date=false + channel + genre         -> empty
		// date=false + channel + title         -> OK
		// date=false + genre + title           -> OK (genre ignored)
		// date=false + channel + genre + title -> OK (genre ignored)

		// String ret = "http://www.dr.dk/tv/api/programmap?";
		String ret = "searchType=startswith";
		ret += getQueryParam("title", title.replaceAll(" ", "%20"));
		ret += getQueryParam("genre", genre.replaceAll(" ", "%20"));
		ret += getQueryParam("channelSlug", channel);
		ret += getQueryParam("includePreviews",Boolean.toString(previews));
		ret += getQueryParam("orderByDate",Boolean.toString(byDate));
		ret += getQueryParam("limit",Integer.toString(limit));
		ret += getQueryParam("offset",Integer.toString(offset));

		String uri = "";
		try {
			uri = URIUtils.createURI("http", "www.dr.dk", 0, "tv/api/programmap",ret,"").toString();
		} catch (URISyntaxException e) {
			System.err.println(e.getMessage());
		}
		System.out.println("Query URI: " + uri);
		return uri;
	}
	public int prompt(String what)
	{
		if (what == null || what.isEmpty()) {
			System.out.print("[Press enter to continue] ");
			new Scanner(System.in).nextLine();
			return 0;
		}

		System.out.print(what + ": ");
		return new Scanner(System.in).nextInt();

	}
	public void menu()
	{
		int res = 0;
		do {
			System.out.println("0: quit\n" +
					"1: Alphabetic\n" +
					"2: Genres\n" +
					"3: Channels\n" +
					"4: Newest");
			res = prompt("What do you want?");
			switch (res) {
			case 0: return;
			case 1: menu(letters);  break;
			case 2: menu(genres);   break;
			case 3: menu(channels); break;
			case 4: menu(newest);   break;
			}
		} while (res > 0);
	}
	public void menu(Mapping m) {
		Set<String>  keys = m.map.keySet();
		List<String> cpy  = new ArrayList<String>(keys);
		Collections.sort(cpy);

		do {
			System.out.printf("%4d: quit\n", 0);
			int i = 1;
			for (String s: cpy) {
				System.out.printf("%4d: %s\n", i++, s);
			}
			int res = prompt("Select");
			if (res == 0) return;

			String s = cpy.get(res-1);
			List<Serie> l = m.get(s);
			if (l == null) continue;

			menu(l);
		} while(true);
	}
	public void menu(List<Serie> m)
	{
		do {
			System.out.printf("%4d: quit\n", 0);
			int i = 1;
			for (Serie s: m) {
				System.out.printf("%4d: %s\n", i++, s.getTitle());
			}
			int res = prompt("Select serie");
			if (res == 0) return;

			Serie s = m.get(res-1);
			if (s == null) continue;
			fillPrograms(s, true);

			Printer p = new Printer();
			p.print(s, Printer.DEFAULT);
			prompt("");

		} while(true);
	}
	/**
	 * Map from string to series objects
	 *
	 *
	 * @author cholm
	 */
	public class Mapping {
		/** Our map */
		public Map<String,List<Serie>> map = new HashMap<String,List<Serie>>();
		/**
		 * Get list of mapped series
		 *
		 * @param key Series slug
		 *
		 * @return List of series
		 */
		public List<Serie> get(String key)
		{
			return map.get(key);
		}
		/**
		 * Get series at a particular position
		 * @param key Series slug
		 * @param idx Index
		 *
		 * @return Serie object or null
		 */
		public Serie get(String key, int idx)
		{
			List<Serie> l = get(key);
			if (l == null) return null;
			return l.get(idx);
		}
		/**
		 * Map a slug to a series
		 *
		 * @param key Series slug
		 * @param s Series
		 */
		public void put(String key, Serie s)
		{
			List<Serie> l = get(key);
			if (l == null) {
				l = new ArrayList<Serie>();
				map.put(key, l);
			}
			// System.out.println("Adding " + s + " to " + l);
			if (!(s == null)) l.add(s);
		}
		/**
		 * Get a set of keys
		 * @return
		 */
		public Set<String> keys()
		{
			return map.keySet();
		}
	}
	/** Rest client */
	private RestClient rest = new RestClient();
	/** Our map */
	public Map<String,Serie> series = new HashMap<String,Serie>();
	/** Map of genres */
	public Mapping genres = new Mapping();
	/** Map of channels */
	public Mapping channels = new Mapping();
	/** Map of genres */
	public Mapping letters = new Mapping();
	/** Map of genres */
	public List<Serie> newest = new ArrayList<Serie>();
	/**
	 * Test program
	 *
	 * @param args
	 */
	public static void main(String[] args) {
		// Log    log = LogFactory.getLog(Extractor.class);
		Logger httpLog = Logger.getLogger("org.apache.http");
		if (httpLog != null) httpLog.setLevel(Level.OFF);

		BasicConfigurator.configure();

		Extractor e = new Extractor();
		e.run();
	}
}
// EOF
